#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# this lesson is based on python 3

# ====SLICE====
weekday = ['Mon', 'Tue', 'Wed', 'Thr', 'Fri']
weekday[0:3] # ['Mon', 'Tue', 'Wed']
weekday[:3] # ['Mon', 'Tue', 'Wed']
weekday[-2:] # ['Thr', 'Fri']
weekday[-2:-1] # ['Thr']
weekday[:] # copy the list

list(range(100))[:10:2] # [0, 2, 4, 6, 8] first 10 items, pick one item every 2
list(range(100))[::10] # [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

(3, 1, 4, 1, 5, 9)[2:4] # (4, 1) tuple can be sliced too

'python'[::2] # 'pto' even string can be sliced



# ====ITERATION==== 
'''
to traverse a list or tuple is called iteration.
in other language like C or java, we often use index to iterate.
but in python, we can iterate an object more directly, and more widely.
'''
for day in weekday:
    print(day)

# you can even iterate objects without index, like dict
for key in {'a':1, 'b':2, 'c':3}: 
	print(key) 
'''
print 
a
b
c
'''
# since dict is unordered, the order may is not as you expected

for value in {'a':1, 'b':2, 'c':3}.values(): 
	print(value) 
'''
print 
1
2
3
'''

for item in {'a':1, 'b':2, 'c':3}.items(): 
	print(item) 
'''
print 
('a', 1)
('b', 2)
('c', 3)
'''

for char in 'ABC': # we can also iterate a string
    print(char)
'''
print 
A
B
C
'''

from collections.abc import Iterable
isinstance('abc', Iterable) # str是否可迭代
True
isinstance([1,2,3], Iterable) # list是否可迭代
True
isinstance(123, Iterable) # 整数是否可迭代
False

for i, value in enumerate(['A', 'B', 'C']): # enumerate can turn a list into index-item couples
     print(i, value)
'''
print
0 A
1 B
2 C
'''

# ====LIST COMPREHENSIONS====
# if we want to generate a list like [1x1, 2x2, 3x3, ..., 10x10], we can do it in just one line:
[x * x for x in range(1, 11)]

# we can even add a conditional to filter only even number
[x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
# [{expression} for {item} in {list} if {condition}]

# when `else` is used, place conditional before `for`
[x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

# we can use 2 layers of loop to list all combinations:
[m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

# expression can make use of multiple variables
d = {'x': 'A', 'y': 'B', 'z': 'C' }
[k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

# ====DICT & SET COMPREHENSIONS====
some_dict = {'a':10, 'b':20, 'c':30}
dict_comp = {k: v ** 2 for k, v in some_dict}

# set can keep only unique value
some_set = {x ** 2 for x in [-1, 0, 1, 2]}

# ====GENERATOR==== 
# generator is just like list comprehension with () and it only compute when it is called
g = (x * x for x in range(10))

next(g) # print 0
next(g) # print 1
next(g) # print 4
next(g) # print 9

for n in g:
    print(n)

# print Fibonacci list 
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b # the function with 'yield' in definition is also a generator
        a, b = b, a + b
        '''
        the line above is equal to:
        tuple = (b, a + b)
        a = tuple[0]
        b = tuple[1]
        '''
        n = n + 1
    return 'done'
# a generator return with it hits 'yield' line, and will begin next loop right after that line

g = fib(5)
next(g) # print 0
next(g) # print 1

for n in fib(6):
    print(n)


# ====ITERAT0R====
# iterator means objects can be invoked with next(), like generator. It's a data stream of lazy computation.
from collections.abc import Iterator
isinstance((x for x in range(10)), Iterator) # return True

# use iter() to turn Iterable into Iterator
isinstance(iter('abc'), Iterator) # return True